package org.example.gateway.config;

import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.client.naming.utils.Chooser;
import com.alibaba.nacos.client.naming.utils.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.stream.Collectors;

/**
 * nacos 权重的负载均衡器
 * {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer # 基于循环的负责均衡}
 * {@link org.springframework.cloud.loadbalancer.core.RandomLoadBalancer # 基于随机的负责均衡实现}
 */
public class NacosWeightRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {


    private static final Log log = LogFactory.getLog(NacosWeightRandomLoadBalancer.class);

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    private final String serviceId;

    public NacosWeightRandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
    }

    /**
     * 根据负载均衡选择下一个服务器
     *
     * @param request
     * @return
     */
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier =
                serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next().map(this::getInstanceResponse);
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        ServiceInstance instance = getHostByRandomWeight(instances);
        return new DefaultResponse(instance);
    }

    /**
     * Return one {@link ServiceInstance} from the host list by random-weight.
     *
     * @see com.alibaba.nacos.client.naming.core.Balancer#getHostByRandomWeight
     */
    private ServiceInstance getHostByRandomWeight(List<ServiceInstance> instances) {
        log.debug("entry randomWithWeight");
        if (instances == null || instances.size() == 0) {
            log.debug("serviceInstances == null || serviceInstances.size() == 0");
            return null;
        }
        // org.example 给Chooser实例起个名字,重写equals时用到了，主要用来比较两个Chooser是否相等
        Chooser<String, ServiceInstance> instanceChooser = new Chooser<>("org.example");
        List<Pair<ServiceInstance>> hostsWithWeight = instances.stream()
                .map(instance -> new Pair<>(instance, getWeight(instance)))
                .collect(Collectors.toList());
        instanceChooser.refresh(hostsWithWeight);
        log.debug("refresh instanceChooser");
        return instanceChooser.randomWithWeight();
    }

    /**
     * Get {@link ServiceInstance} weight metadata.
     *
     * @see NacosServiceDiscovery#hostToServiceInstance
     */
    private double getWeight(ServiceInstance instance) {
        return Double.parseDouble(instance.getMetadata().get("nacos.weight"));
    }
}
